﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WenSkin.Controls;

public class NestBox : Control
{
    private PoiList pois;

    public NestBox()
    {
        base.SetStyle(
            ControlStyles.UserPaint |
            ControlStyles.DoubleBuffer |
            ControlStyles.OptimizedDoubleBuffer |
            ControlStyles.AllPaintingInWmPaint |
            ControlStyles.ResizeRedraw |
            ControlStyles.SupportsTransparentBackColor, true);
        base.UpdateStyles();
        this.BackColor = Color.Transparent;
    }


    [Category("文"), Description("限制移动只允许内部区域"), DefaultValue(false)]
    public bool IsLimitRec { get; set; } = true;


    public float L { get; set; } = 2400;
    public float W { get; set; } = 1200;
    public float H { get; set; } = 18;

    private float Sc => this.Width / this.L;



    public Poi SelectedPoi { get; set; }


    protected override void OnSizeChanged(EventArgs e)
    {
        base.OnSizeChanged(e);
        this.Width = (int)(this.Height * (L / W));
    }


    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);


        Graphics g = e.Graphics.SetGDIHigh();


        //画底层框
        using Pen p = new Pen(Color.Gray, 3);
        //画底层框
        using Pen px = new Pen(Color.Gray, 1)
        {
            DashStyle = DashStyle.Dot,
        };
        g.DrawRectangle(p, 0, 0, this.Width - 1, this.Height - 1);

        for (int i = 0; i < this.W; i += 100)
        {
            g.DrawLine(px, 0, i * Sc, this.Width, i * Sc);
        }
        for (int i = 0; i < this.L; i += 100)
        {
            g.DrawLine(px, i * Sc, 0, i * Sc, this.Height);
        }

        DrawPoi(g);
    }


    private void DrawPoi(Graphics g)
    {
        if (this.Pois.Count <= 0)
        {
            return;
        }


        foreach (var poi in Pois)
        {
            g.FillPath(Brushes.Black, poi.GetGraphicsPath(this.Sc));
            if (poi == SelectedPoi)
            {
                using var p = new Pen(Color.Red, 3)
                {
                    Alignment = PenAlignment.Inset,
                    DashStyle = DashStyle.Dot,
                };
                g.DrawPath(p, poi.GetGraphicsPath(this.Sc));

                g.FillRectangle(Brushes.Aqua, new RectangleF((poi.BasicPoint.X) * this.Sc, (poi.BasicPoint.Y) * this.Sc, 8, 8));
            }
        }
    }



    #region 鼠标操作

    private bool isMouseDown = false;
    private PointF mouseDownPoint = new PointF(0, 0);

    //鼠标进入控件
    protected override void OnMouseMove(MouseEventArgs e)
    {
        base.OnMouseMove(e);

        var p = new PointF(e.X / Sc, e.Y / Sc);

        if (this.Pois.IsVisible(p))
        {
            Cursor = Cursors.SizeAll;
        }
        else
        {
            Cursor = Cursors.Default;
        }



        var x = e.X - mouseDownPoint.X;
        var y = e.Y - mouseDownPoint.Y;

        if (this.SelectedPoi != null && isMouseDown)
        {
            var mx = (int)(x / Sc);
            var my = (int)(y / Sc);

            if (this.SelectedPoi.IsOffsetBasicPointIsVisible(mx, my, new RectangleF(0, 0, L, W)))
            {
                this.SelectedPoi.OffsetBasicPoint(mx, my);

                var po = this.SelectedPoi.BasicPoint;

                //判断基点是否考进指定点

                //获取最小值
                var mmx = this.NoSelectedPointFs.OrderBy(p => Math.Abs(p.X - po.X)).First();
                var mmy = this.NoSelectedPointFs.OrderBy(p => Math.Abs(p.Y - po.Y)).First();



                //判断当前坐标是否接近整数
                var px = Math.Abs((po.X + mx) % 100);
                var py = Math.Abs((po.Y + my) % 100);

                var nx = Math.Abs(mmx.X - po.X) <= 10 ? mmx.X : px <= 5 ? po.X - px : px >= 95 ? po.X - px + 100 : po.X;
                var ny = Math.Abs(mmy.Y - po.Y) <= 10 ? mmy.Y : py <= 5 ? po.Y - py : py >= 95 ? po.Y - py + 100 : po.Y;

                //Console.WriteLine(mmx);
                //Console.WriteLine(mmy);
                //Console.WriteLine(po);
                //Console.WriteLine();
                this.SelectedPoi.BasicPoint = new PointF(nx, ny);
            }

            this.Invalidate();
            mouseDownPoint = e.Location;
        }
    }

    protected override void OnMouseDown(MouseEventArgs e)
    {
        base.OnMouseDown(e);
        var p = new PointF(e.X / Sc, e.Y / Sc);

        this.isMouseDown = true;
        this.mouseDownPoint = e.Location;

        this.SelectedPoi = this.Pois.GetMousePoi(p);
        if(this.SelectedPoi!=null)
        {
            NoSelectedPointFs = GetNoSelectedPoiPs();
        }
        this.Invalidate(true);
    }

    protected override void OnMouseUp(MouseEventArgs e)
    {
        base.OnMouseUp(e);
        this.isMouseDown = false;
    }




    #endregion



    private List<PointF> NoSelectedPointFs { get; set; } = new List<PointF>();

    private List<PointF> GetNoSelectedPoiPs()
    {
        var ps = new List<PointF>();
        foreach (var p in this.Pois.FindAll(x => x != this.SelectedPoi).ToList())
        {
            ps.AddRange(p.GetPointFs());
        }
        return ps;
    }



    //检查是否重叠
    public void Rrr()
    {
        var ls = new List<Poi>();

        for (int i = 0; i < this.Pois.Count; i++)
        {
            var gp = this.Pois[i].GetGraphicsPath();

            for (int j = i + 1; j < this.Pois.Count; j++)
            {
                var ps = this.Pois[j].GetPointFs();
                foreach (var p in ps)
                {
                    if (gp.IsVisible(p))
                    {
                        ls.Add(this.pois[j]);
                        ls.Add(this.pois[i]);
                        break;
                    }
                }
            }
        }
        var xs = ls.Distinct();
    }


    public PoiList Pois => this.pois ??= new PoiList(this);


    public class PoiList : List<Poi>
    {
        private NestBox owner;

        public PoiList(NestBox owner)
        {
            this.owner = owner;
        }

        public bool IsVisible(PointF p)
        {
            foreach (var poi in Enumerable.Reverse(this))
            {
                if (poi.GetGraphicsPath().IsVisible(p))
                    return true;
            }
            return false;
        }

        public Poi GetMousePoi(PointF p)
        {
            foreach (var poi in Enumerable.Reverse(this))
            {
                if (poi.GetGraphicsPath().IsVisible(p))
                    return poi;
            }
            return null;
        }

        public new void Add(Poi poi)
        {
            base.Add(poi);
            owner.Invalidate();
        }
    }

    public class Poi
    {

        private List<PointF> points;
        public List<PointF> Points => points ??= new List<PointF>();

        public PointF BasicPoint { get; set; } = new PointF(0, 0);


        public int Rotate { get; set; } = 0;

        public PointF[] GetPointFs(float sc = 1)
        {
            return Points.Select(x => new PointF((x.X + this.BasicPoint.X) * sc, (x.Y + this.BasicPoint.Y) * sc)).ToArray();
        }



        public GraphicsPath GetGraphicsPath(float sc = 1)
        {
            var gp = new GraphicsPath();
            gp.AddLines(GetPointFs(sc));
            gp.CloseAllFigures();

            var rec = gp.GetBounds();
            var matrix = new Matrix();
            matrix.RotateAt(this.Rotate, new PointF(rec.X + rec.Width / 2, rec.Y + rec.Height / 2));
            gp.Transform(matrix);
            return gp;
        }

        public void SetBasicPoint(PointF p)
        {
            this.BasicPoint = p;
        }

        public void OffsetBasicPoint(float x, float y)
        {
            this.BasicPoint = new PointF(this.BasicPoint.X + x, this.BasicPoint.Y + y);
        }


        public bool IsOffsetBasicPointIsVisible(float x, float y, RectangleF inrec)
        {
            var pos = Points.Select(it => new PointF(it.X + this.BasicPoint.X + x, it.Y + this.BasicPoint.Y + y)).ToArray();
            var gp = new GraphicsPath();
            gp.AddLines(pos);
            gp.CloseAllFigures();

            var rec = gp.GetBounds();
            var matrix = new Matrix();
            matrix.RotateAt(this.Rotate, new PointF(rec.X + rec.Width / 2, rec.Y + rec.Height / 2));
            gp.Transform(matrix);

            return inrec.Contains(gp.GetBounds());
        }
    }
}